namespace Ex
{
    using System.Diagnostics;
    using KeraLua;

    public class ExCsFunction : IExCsFunction
    {
        protected readonly EnvLua env;

        public EnvLua CurrentLua
        {
            get
            {
                return env;
            }
        }

        protected readonly string func_name;

        public string Name
        {
            get
            {
                return func_name;
            }
        }

        // public override string ToString()
        // {
        //     return $"{base.ToString()}({Name})";
        // }

        protected readonly Func<IndefiniteVarType, IndefiniteVarType> func;

        public Func<IndefiniteVarType, IndefiniteVarType> OriginalFunction
        {
            get => func;
        }

        private LuaFunction luaFunction;
        
        /// <summary>
        /// 若值为-1，说明是不定参数
        /// </summary>
        private readonly int? param_count;


        public ExCsFunction(
            EnvLua lua,
            string func_name,
            Func<IndefiniteVarType, IndefiniteVarType> func,
            UsableLuaType[] param_type
            )
        {
            this.env = lua;
            this.func = func;
            this.func_name = func_name;
            this.param_count  = param_type.Length;
            this.luaFunction = CreateLuaFunction(() => IndefiniteVarType.FetchFromLuaStack(env, param_type));
        }
        
        /// <summary>
        /// 用于不定参数
        /// </summary>
        /// <param name="lua"></param>
        /// <param name="func_name"></param>
        /// <param name="func"></param>
        public ExCsFunction(
            EnvLua lua,
            string func_name,
            Func<IndefiniteVarType, IndefiniteVarType> func
        )
        {
            this.env = lua;
            this.func = func;
            this.func_name = func_name;
            this.param_count  = -1;
            lock (env)
            {
                this.luaFunction = CreateLuaFunction(() => IndefiniteVarType.FetchFromLuaStack(env, env.Current.GetTop()));
            }
        }

        protected virtual void ExceptionHandle(Action action, string addon_msg)
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                lock (env)
                {
                    if (e is InvalidOperationException)
                    {
                        var order_msg = e.Data.Contains("arg_order") ? string.Format(" at {0}", e.Data["arg_order"]) : "";
                        env.Current.Error(addon_msg + order_msg + '\n' + e.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
            }
        }

        protected virtual LuaFunction CreateLuaFunction(Func<IndefiniteVarType> param_getter)
        {
            return target =>
            {
                IndefiniteVarType? param = null, ret = null;
                ExceptionHandle(
                    () => param = param_getter(),
                    "parameter error"
                );
                Debug.Assert(param != null);
                ExceptionHandle(
                    () => ret = func(param),
                    "running error"
                );
                Debug.Assert(ret != null);
                ret.PushToLuaStack(env);
                return ret.Count;
            };
        }


        public IndefiniteVarType Call(UsableLuaType[] result_type, params object[] args)
        {
            return CallFromArray(result_type, args);
        }

        public virtual IndefiniteVarType CallFromArray(UsableLuaType[] result_type, object[] args)
        {
            return func(new IndefiniteVarType(args));
        }

        public virtual IndefiniteVarType CallFromArray(int return_num, object[] args)
        {
            return func(new IndefiniteVarType(args));
        }

        public void PushToLuaStack()
        {
            lock (env)
            {
                env.Current.PushCFunction(luaFunction);
            }
        }
    }
}